﻿using System;
using System.ComponentModel;
using System.Linq;
using System.Windows.Input;
using Cloak.Globalization;

namespace Cloak.Mvvm
{
	/// <summary>
	/// Represents a view of the status of an asynchronous operation
	/// </summary>
	public class OperationModel : ViewModel
	{
		private OperationStatus _status;

		/// <summary>
		/// Initializes an instance of <see cref="OperationModel"/>
		/// </summary>
		public OperationModel()
		{
			ClearErrorCommand = new MethodCommand(OnClearError);
		}

		/// <summary>
		/// Gets the current status of the operation
		/// </summary>
		public OperationStatus Status
		{
			get { return _status; }
			private set { SetProperty("Status", ref _status, value); }
		}

		/// <summary>
		/// Gets whether <see cref="Status"/> is <see cref="OperationStatus.InProgress"/>
		/// </summary>
		public bool IsInProgress
		{
			get { return _status == OperationStatus.InProgress; }
		}

		/// <summary>
		/// Gets the command which clears the current error state, indicating it has been acknowledged
		/// </summary>
		public ICommand ClearErrorCommand { get; private set; }

		/// <summary>
		/// Move the operation into the <see cref="OperationStatus.InProgress"/> state
		/// </summary>
		/// <exception cref="InvalidOperationException">Thrown if the operation is not in the <see cref="OperationStatus.Idle"/> state</exception>
		public void OnStarting()
		{
			UpdateStatus(OperationStatus.InProgress, OperationStatus.Idle);
		}

		/// <summary>
		/// Move the operation back to the <see cref="OperationStatus.Idle"/> state
		/// </summary>
		/// <exception cref="InvalidOperationException">Thrown if the operation is not in the <see cref="OperationStatus.InProgress"/> state</exception>
		public void OnCompleted()
		{
			UpdateStatus(OperationStatus.Idle, OperationStatus.InProgress);
		}

		/// <summary>
		/// Move the operation into the <see cref="OperationStatus.TechnicalError"/> state
		/// </summary>
		/// <exception cref="InvalidOperationException">Thrown if the operation is not in the <see cref="OperationStatus.InProgress"/> state</exception>
		public void OnTechnicalError()
		{
			UpdateStatus(OperationStatus.TechnicalError, OperationStatus.InProgress);
		}

		/// <summary>
		/// Move the operation into the <see cref="OperationStatus.TechnicalError"/> state
		/// </summary>
		/// <exception cref="InvalidOperationException">Thrown if the operation is not in the <see cref="OperationStatus.InProgress"/> state</exception>
		public void OnDomainError()
		{
			UpdateStatus(OperationStatus.DomainError, OperationStatus.InProgress);
		}

		private void UpdateStatus(OperationStatus newStatus, params OperationStatus[] validCurrentStatuses)
		{
			if(!validCurrentStatuses.Contains(Status))
			{
				throw new InvalidOperationException(Resources.InvalidOperationStatusUpdate.FormatInvariant(
					Status,
					newStatus,
					String.Join(", ", validCurrentStatuses)));
			}

			Status = newStatus;
		}

		/// <summary>
		/// Handles changes in the properties of this object
		/// </summary>
		protected override void OnPropertyChanged(PropertyChangedEventArgs e)
		{
			base.OnPropertyChanged(e);

			if(e.PropertyName == "Status")
			{
				RaisePropertyChanged("IsInProgress");
			}
		}

		private void OnClearError()
		{
			UpdateStatus(OperationStatus.Idle, OperationStatus.TechnicalError, OperationStatus.DomainError);
		}
	}
}